<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <meta name="keywords" content="imlgw,半岛铁盒,blog,Java博客,程序员,个人博客,java開發,程序員,個人博客,Java">
    <meta name="description" content="大悲无泪，大悟无言，大笑无声。">
    <meta name="author" content="Resolmi">
    
    <title>
        
            Volatile关键字详解 |
        
        Tadow
    </title>
    
<link rel="stylesheet" href="/css/style.css">

    <link rel="shortcut icon" href="https://static.imlgw.top/blog/20210731/BtJz541CcmJU.ico">
    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.4.3/source/css/font-awesome.min.css">
    <script id="hexo-configurations">
    let KEEP = window.KEEP || {};
    KEEP.hexo_config = {"hostname":"imlgw.top","root":"/","language":"zh-CN","path":"search.json"};
    KEEP.theme_config = {"toc":{"enable":true,"number":true,"expand_all":true,"init_open":true},"style":{"primary_color":"#0066CC","avatar":"https://static.imlgw.top/blog/20210731/3C7hCSRR3lfq.png","favicon":"https://static.imlgw.top/blog/20210731/BtJz541CcmJU.ico","article_img_align":"left","left_side_width":"260px","content_max_width":"920px","hover":{"shadow":false,"scale":true},"first_screen":{"enable":true,"background_img":"/images/image.svg","description":"Keep It Simple & Stupid."},"scroll":{"progress_bar":{"enable":true},"percent":{"enable":true}}},"local_search":{"enable":true,"preload":false},"code_copy":{"enable":true,"style":"default"},"pjax":{"enable":true},"lazyload":{"enable":true},"version":"3.4.3"};
    KEEP.language_ago = {"second":"%s 秒前","minute":"%s 分钟前","hour":"%s 小时前","day":"%s 天前","week":"%s 周前","month":"%s 月前","year":"%s 年前"};
  </script>
<meta name="generator" content="Hexo 5.4.0"><link rel="stylesheet" href="/css/prism.css" type="text/css"></head>


<body>
<div class="progress-bar-container">
    
        <span class="scroll-progress-bar"></span>
    

    
        <span class="pjax-progress-bar"></span>
        <span class="pjax-progress-icon">
            <i class="fas fa-circle-notch fa-spin"></i>
        </span>
    
</div>


<main class="page-container">

    

    <div class="page-main-content">

        <div class="page-main-content-top">
            <header class="header-wrapper">

    <div class="header-content">
        <div class="left">
            
            <a class="logo-title" href="/">
                Tadow
            </a>
        </div>

        <div class="right">
            <div class="pc">
                <ul class="menu-list">
                    
                        <li class="menu-item">
                            <a class=""
                               href="/"
                            >
                                首页
                            </a>
                        </li>
                    
                        <li class="menu-item">
                            <a class=""
                               href="/archives"
                            >
                                归档
                            </a>
                        </li>
                    
                        <li class="menu-item">
                            <a class=""
                               href="/categories"
                            >
                                分类
                            </a>
                        </li>
                    
                        <li class="menu-item">
                            <a class=""
                               href="/sbe"
                            >
                                订阅
                            </a>
                        </li>
                    
                        <li class="menu-item">
                            <a class=""
                               href="/links"
                            >
                                友链
                            </a>
                        </li>
                    
                        <li class="menu-item">
                            <a class=""
                               href="/about"
                            >
                                关于
                            </a>
                        </li>
                    
                    
                        <li class="menu-item search search-popup-trigger">
                            <i class="fas fa-search"></i>
                        </li>
                    
                </ul>
            </div>
            <div class="mobile">
                
                    <div class="icon-item search search-popup-trigger"><i class="fas fa-search"></i></div>
                
                <div class="icon-item menu-bar">
                    <div class="menu-bar-middle"></div>
                </div>
            </div>
        </div>
    </div>

    <div class="header-drawer">
        <ul class="drawer-menu-list">
            
                <li class="drawer-menu-item flex-center">
                    <a class=""
                       href="/">首页</a>
                </li>
            
                <li class="drawer-menu-item flex-center">
                    <a class=""
                       href="/archives">归档</a>
                </li>
            
                <li class="drawer-menu-item flex-center">
                    <a class=""
                       href="/categories">分类</a>
                </li>
            
                <li class="drawer-menu-item flex-center">
                    <a class=""
                       href="/sbe">订阅</a>
                </li>
            
                <li class="drawer-menu-item flex-center">
                    <a class=""
                       href="/links">友链</a>
                </li>
            
                <li class="drawer-menu-item flex-center">
                    <a class=""
                       href="/about">关于</a>
                </li>
            
        </ul>
    </div>

    <div class="window-mask"></div>

</header>


        </div>

        <div class="page-main-content-middle">

            <div class="main-content">

                
                    <div class="fade-in-down-animation">
    <div class="article-content-container">

        <div class="article-title">
            <span class="title-hover-animation">Volatile关键字详解</span>
        </div>

        
            <div class="article-header">
                <div class="avatar">
                    <img src="https://static.imlgw.top/blog/20210731/3C7hCSRR3lfq.png">
                </div>
                <div class="info">
                    <div class="author">
                        <span class="name">Resolmi</span>
                        
                            <span class="author-label">BOSS</span>
                        
                    </div>
                    <div class="meta-info">
                        <div class="article-meta-info">
    <span class="article-date article-meta-item">
        <i class="fas fa-edit"></i>&nbsp;2019-04-29 00:00:00
    </span>
    
        <span class="article-categories article-meta-item">
            <i class="fas fa-folder"></i>&nbsp;
            <ul>
                
                    <li>
                        <a href="/categories/%E5%B9%B6%E5%8F%91/">并发</a>&nbsp;
                    </li>
                
            </ul>
        </span>
    
    
        <span class="article-tags article-meta-item">
            <i class="fas fa-tags"></i>&nbsp;
            <ul>
                
                    <li>
                        <a href="/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/">多线程</a>&nbsp;
                    </li>
                
                    <li>
                        | <a href="/tags/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/">并发编程</a>&nbsp;
                    </li>
                
            </ul>
        </span>
    

    
    
        <span class="article-wordcount article-meta-item">
            <i class="fas fa-file-word"></i>&nbsp;<span>5.1k 字</span>
        </span>
    
    
        <span class="article-min2read article-meta-item">
            <i class="fas fa-clock"></i>&nbsp;<span>18 分钟</span>
        </span>
    
    
        <span class="article-pv article-meta-item">
            <i class="fas fa-eye"></i>&nbsp;<span id="busuanzi_value_page_pv"></span>
        </span>
    
</div>

                    </div>
                </div>
            </div>
        

        <div class="article-content markdown-body">
            <h2 id="JMM-amp-CPU缓存"><a href="#JMM-amp-CPU缓存" class="headerlink" title="JMM&amp;CPU缓存"></a>JMM&amp;CPU缓存</h2><h3 id="CPU缓存"><a href="#CPU缓存" class="headerlink" title="CPU缓存"></a>CPU缓存</h3><p>其实这个并不是指某一个具体的部件，<code>寄存器(Register)</code>，<code>高速缓存(Cache)</code>，<code>写缓冲器(Store Buffer)</code>，<code>无效化队列(Invalidate Queue)</code>等等都可以称为 CPU缓存。</p>
<h4 id="为什么要有CPU缓存？"><a href="#为什么要有CPU缓存？" class="headerlink" title="为什么要有CPU缓存？"></a>为什么要有CPU缓存？</h4><p><code>缓存</code>通常意义下都是为了加快速度，这里同样也是，因为CPU的速度比<code>主内存(RAM)</code>快很多，<code>主内存</code>会拖CPU后腿影响整体的效率，所以缓存就出现了，缓存的速度比<code>主内存</code>快很多(造价高)CPU会直接通过缓存来对主内存进行读写操作，所以缓存里面实际上相当于是<code>主内存</code>的副本。</p>
<h4 id="使用CPU缓存带来的问题"><a href="#使用CPU缓存带来的问题" class="headerlink" title="使用CPU缓存带来的问题"></a>使用CPU缓存带来的问题</h4><p>正常情况下CPU执行计算的过程如下</p>
<p>1️⃣程序以及数据被加载到主内存</p>
<p>2️⃣指令和数据被加载到CPU缓存</p>
<p>3️⃣CPU执行指令，把结果写到高速缓存</p>
<p>4️⃣高速缓存中的数据写回主内存</p>
<p>如果是单核CPU，上面的步骤没有任何问题，但如果是多核CPU就可能会出现一些意料之外的问题，假设有两个核</p>
<p>下面这种情况也是有可能发生的</p>
<p>1️⃣核0读取了一个字节，根据局部性原理，它相邻的字节同样被被读入核0的缓存</p>
<p>2️⃣核1做了上面同样的工作，这样核0与核1的缓存拥有同样的数据</p>
<p>3️⃣核0修改了那个字节，被修改后，那个字节被写回核0的缓存，但是该信息并没有写回主存</p>
<p>4️⃣核1访问该字节，由于核0并未将数据写回主存，数据不同步</p>
<h4 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h4><p>🔶LOCK# 总线锁，效率很低，同时只能有一个CPU对内存操作，其他的CPU只能干等着</p>
<p>🔶缓存一致性<code>协议</code>，缓存一致性协议有多种，<code>MESI</code>协议是当前最主流的缓存一致性协议</p>
<p><img  
                     lazyload
                     src="/images/loading.svg"
                     data-src="http://static.imlgw.top///20190411/GYdTPdVBGCQB.png?imageslim"
                      alt="MESI状态"
                >日常处理的大多数计算机设备都属于<code>嗅探(snooping)</code>协议，CPU缓存不仅仅在做内存传输的时候才与总线打交道，而是不停在嗅探总线上发生的数据交换，跟踪其他缓存在做什么。所以当一个缓存代表它所属的处理器去<code>读写内存</code>时，其它处理器都会得到通知，它们以此来使自己的缓存保持同步。只要某个处理器一<code>写内存</code>，其它处理器马上知道这块内存在它们的缓存段中<code>已失效(Invaid)</code>，如果这个时候有处理器想<code>读内存</code>(会被立即察觉到，因为一直在嗅探总线)，那么已修改的缓存行(Cache line)就会立即刷新到主存中，然后设置为<code>Share</code>状态，这样一来读取到的数据就不是脏数据了。</p>
<p>再放一张 处理器&amp;缓存&amp;主内存交互的图（来自组成原理书上的图）</p>
<p><img  
                     lazyload
                     src="/images/loading.svg"
                     data-src="http://static.imlgw.top///20190415/lJzPTl3yQWBn.jpg?imageslim"
                      alt="Cache基本结构"
                ></p>
<p>既然有了MESI协议，是不是就不需要volatile的可见性语义了？当然不是</p>
<ul>
<li><strong>并不是所有的硬件架构都提供了相同的一致性保证，JVM需要volatile统一语义</strong>（就算是MESI，也只解决CPU缓存层面的问题，没有涉及其他层面）。</li>
<li>可见性问题不仅仅局限于CPU缓存内，JVM自己维护的<code>内存模型</code>中也有可见性问题。使用volatile做标记，可以解决JVM层面的可见性问题。</li>
<li><a class="link"   target="_blank" rel="noopener" href="https://www.zhihu.com/question/277395220" >这个回答应该很好的解释了<i class="fas fa-external-link-alt"></i></a> 大概就是缓存一致性协议并不能保证实时性，而有时候我们需要保证严格的实时性</li>
</ul>
<h3 id="Java内存模型-JMM"><a href="#Java内存模型-JMM" class="headerlink" title="Java内存模型(JMM)"></a>Java内存模型(JMM)</h3><blockquote>
<p>为了屏蔽各个操作系统和硬件的差异，使得 Java 程序在所有平台下都能达到一致的内存访问效果，所以 Java 虚拟机定义了一种 Java 内存模型。</p>
</blockquote>
<p>Java内存模型(即Java Memory Model，简称JMM)本身是一种抽象的概念，并不真实存在，它描述的是一组规则或规范，通过这组规范定义了程序中各个变量（包括实例字段，静态字段和构成数组对象的元素）的访问方式。（这里的变量不包括局部变量和方法参数，因为那是线程私有的，不会产生竞争）</p>
<p>Java 虚拟机规定所有的变量都存储在主内存（Main Memory），每个线程都有自己的工作线程（Work Memory 有些地方称为线程栈）。</p>
<p>线程的工作内存中保存了使用到的变量的主内存副本拷贝，线程对变量的操作是在自己的工作内存中，首先要将变量从主内存拷贝的自己的工作内存空间，然后对变量进行操作，操作完成后再将变量写回主内存，而不能直接对主内存的变量进行读取赋值。</p>
<p>不同线程之间无法直接访问对方工作内存中的变量，需要通过主内存来进行传递。</p>
<p><img  
                     lazyload
                     src="/images/loading.svg"
                     data-src="http://static.imlgw.top///20190411/PjL8vV724vXx.png?imageslim"
                      alt="JMM"
                ></p>
<p>（来自 <a class="link"   target="_blank" rel="noopener" href="https://blog.csdn.net/javazejian/article/details/72772461" >zejian<i class="fas fa-external-link-alt"></i></a>）</p>
<p>工作内存实际上就是对上面<strong>CPU缓存</strong>的抽象。</p>
<h4 id="内存间交互"><a href="#内存间交互" class="headerlink" title="内存间交互"></a>内存间交互</h4><p>Java 内存模型定义了 8 个操作来完成主内存和工作内存的交互操作。<br>read：把一个变量的值从主内存传输到工作内存中<br>load：在 read 之后执行，把 read 得到的值放入工作内存的变量副本中<br>use：把工作内存中一个变量的值传递给执行引擎<br>assign：把一个从执行引擎接收到的值赋给工作内存的变量<br>store：把工作内存的一个变量的值传送到主内存中<br>write：在 store 之后执行，把 store 得到的值放入主内存的变量中<br>lock：作用于主内存的变量<br>unlock：对应lock</p>
<p><strong>JMM</strong>还定义了一些关于<code>happens-before</code>关系的规则如下</p>
<ul>
<li><p>代码的执行顺序，编写在前面的发生在编写在后面的。</p>
</li>
<li><p>unlock 必须发生在lock之后</p>
</li>
<li><p>volatile 修饰的 写操作先发生在读之前</p>
</li>
<li><p>传递规则 ，A 先于B B先于C A肯定先于C</p>
</li>
<li><p>线程启动规则，start肯定现场发生与run</p>
</li>
<li><p>线程中断方法，interrupt 必须发生在捕获之前</p>
</li>
<li><p>对象的初始化必须发生在finalize前</p>
</li>
<li><p>线程终结规则，所有操作都发生在线程死亡前</p>
</li>
</ul>
<p><a class="link"   target="_blank" rel="noopener" href="https://www.logicbig.com/tutorials/core-java-tutorial/java-multi-threading/happens-before.html" >参考<i class="fas fa-external-link-alt"></i></a></p>
<h2 id="Volatile干了什么？"><a href="#Volatile干了什么？" class="headerlink" title="Volatile干了什么？"></a>Volatile干了什么？</h2><h3 id="保证可见性"><a href="#保证可见性" class="headerlink" title="保证可见性"></a>保证可见性</h3><p>这里我们来看一个具体的Demo。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//线程1</span></span><br><span class="line"><span class="keyword">boolean</span> stop = <span class="keyword">false</span>;</span><br><span class="line"><span class="keyword">while</span>(!stop)&#123;</span><br><span class="line">    <span class="comment">//doSomething();</span></span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line"><span class="comment">//线程2</span></span><br><span class="line">stop = <span class="keyword">true</span>;</span><br></pre></td></tr></table></figure>

<p>其实在上一篇 <a href="http://imlgw.top/2019/04/07/java-duo-xian-cheng-xue-xi-bi-ji/">Java多线程基础</a> 里面 “优雅的结束线程” 里面有类似的代码</p>
<p><img  
                     lazyload
                     src="/images/loading.svg"
                     data-src="http://static.imlgw.top///20190428/LQuwmhuMiwxp.png?imageslim"
                      alt="mark"
                ></p>
<p>当时没有说明为啥要加<code>Volatile</code>，其实这里上面的代码如果不给状态量加上 <code>volatile</code>  并且用<code>server</code>模式运行有可能就会陷入死循环，即使在主线程里面将<code>isCancel</code>修改为<code>true</code>仍然无法结束，线程陷入了死循环，永远无法停止！！!为什么会这样？？？</p>
<p><img  
                     lazyload
                     src="/images/loading.svg"
                     data-src="http://static.imlgw.top///20190410/nCcFbqyGiqIz.png?imageslim"
                      alt="mark"
                ></p>
<p>🔸 在<code>server</code>模式下JIT对我们的代码进行了优化（这也是为什么要用server模式运行的原因，<strong>Client VM的编译器没有像Server VM一样执行许多复杂的优化算法</strong>）。它会将代码优化为类似下面这样的效果</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span>(!stop)&#123;</span><br><span class="line"> 	<span class="keyword">while</span> (stop)&#123;</span><br><span class="line">		<span class="comment">//do something....</span></span><br><span class="line"> 	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>JIT</code>认为只有一个线程对其进行访问，所以为了避免重复的读取状态变量<code>stop</code>就将代码进行了 <a class="link"   target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/%E5%BE%AA%E7%8E%AF%E4%B8%8D%E5%8F%98%E4%BB%A3%E7%A0%81%E5%A4%96%E6%8F%90" >循环不变表达式外提<i class="fas fa-external-link-alt"></i></a>（wikipedia），而这恰恰导致了死循环</p>
<p>🔸另一方面，也和计算机的储存系统有关，也就是上面<code>CPU缓存</code>的问题中提到的，这里 <code>stop</code>就是共享变量，当线程①和②运行的时候先将主内存的<code>stop</code>拷贝了一份到<code>工作内存</code>中，其中一个线程修改了<code>stop</code>的值但是其他的线程无法感知到这个变化就可能会陷入死循环。</p>
<p>📢 <code>volatile</code>在这里起到的作用就是</p>
<p>① 阻止<code>JIT</code>的异常优化</p>
<p>② 在一个线程修改了<code>volatile</code>修饰的共享变量后会<strong>立即刷新到主内存</strong>当中，这个过程称为_冲刷处理器缓存_。如果一个线程在读<code>voaltile</code>修饰的变量就会使相应的处理器<strong>必须从主内存中进行同步</strong>，这个过程称之为_刷新处理器缓存_，从而保证了可见性，通俗的讲就是<code>读必须从主内存中读，写必须同步到主内存中</code>。</p>
<h3 id="保证有序性"><a href="#保证有序性" class="headerlink" title="保证有序性"></a>保证有序性</h3><p><strong>重排序</strong></p>
<p>提到有序性就不得不说重排序，先来看一个<code>Demo</code></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">boolean</span> isReady=<span class="keyword">false</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">writer</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> data=getFromXxx(); <span class="comment">//①</span></span><br><span class="line">    isReady=<span class="keyword">true</span> 		<span class="comment">//②</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">reader</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(isReady)&#123;</span><br><span class="line">        <span class="comment">//doSth</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这一看似乎没有什么问题，writer线程完成后开始read，问题就出来这里，<strong>有可能data数据还没获取到，isReady就已经是true了</strong>也就是说②和①的执行交换了顺序，也就是所谓的<strong>重排序</strong>，这样的重排序将会导致不可预知的错误，而导致这种现象的来源很多，比如编译器(JIT)，处理器和存储子系统(Cache)，至于为什么要重排序，主要还是为了提升性能，当然重排序对单线程来讲是没有影响的(有影响那还得了😂)</p>
<p><strong>其实不只是上面那种比较显而易见的重排序，还有下面这种比较隐含的</strong></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SingletonObjectPlus</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span>  SingletonObjectPlus singletonObjectPlus =<span class="keyword">null</span>;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> SingletonObjectPlus <span class="title">getSingletonObject3</span><span class="params">()</span></span>&#123;</span><br><span class="line">    	<span class="keyword">if</span>(singletonObjectPlus==<span class="keyword">null</span>)&#123;</span><br><span class="line">        	<span class="keyword">synchronized</span>(SingletonObject.class)&#123;</span><br><span class="line">            	<span class="keyword">if</span>(singletonObjectPlus==<span class="keyword">null</span>)&#123;</span><br><span class="line">                	singletonObjectPlus= <span class="keyword">new</span> SingletonObjectPlus();</span><br><span class="line">            	&#125;</span><br><span class="line">        	&#125;</span><br><span class="line">    	&#125;</span><br><span class="line">     	<span class="keyword">return</span> singletonObjectPlus;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>熟悉的朋友可能看出来了，这是一个DCL单例，那它有什么问题呢？它也会被重排序么？那么会在哪里重排序呢？</p>
<p>上述代码确实有问题，问题在**new SingletonObjectPlus();**里面，实际上new这个操作可以划分为如下好几步</p>
<ol>
<li>分配对象所需的空间 <code>objRef=allocate(SingleObjectPlus.class);</code></li>
<li>初始化引用的对象 <code>invokeConstructor(objRef);</code></li>
<li>设置<code>singletonObjectPlus</code>指向刚分配的内存地址<code>singletonObjectPlus=objRef</code></li>
</ol>
<p>而这些步骤有可能就会被重排序，比如将③排到②之前，也就是对象还没有初始化完成就会被返回(已经分配空间了，就不为null了)，这样在<code>最外层if</code>判断的时候就可能会直接返回一个初始化未完成的对象</p>
<blockquote>
<p>发生这样重排序的概率很低，并不是必然出现的，重排序也不是随意的顺序调整，而是按照一定的规则去重排序，保证不会对单线程程序运行结果造成影响，显而易见，如果两条语句之间存在依赖关系，肯定是不会重排序的，具体就是两条语句访问同一个变量地址，至少有一条为写操作，那么这两条指令就存在依赖关系就不会被重排序比如 x=1;x=2;这样的 就不会被重排序。</p>
</blockquote>
<p>其实上面的问题都很好解决，只要在<strong>isReady</strong>和<strong>singletonObjectPlus</strong>上加上<code>volatile</code>就ok了，在这里volatile会禁止指令的重排序（底层通过调用处理器提供的内存屏障）</p>
<h3 id="保障Long-Double变量写的原子性"><a href="#保障Long-Double变量写的原子性" class="headerlink" title="保障Long/Double变量写的原子性"></a>保障Long/Double变量写的原子性</h3><p>这一点其实很容易被遗忘，实际上Java对所有除了Long和Double的变量的<strong>读写</strong>操作都是原子性的，包括基础类型(byte，boolean，short，float，和int)和引用类型。因为Double和Long类型的变量会占用64位，如果在<code>32位机器</code>上JVM对这种变量的读写可能就是会被分解为两个操作而在多线程的情况下就会出现问题，这里就不做演示了，知道有这么个事就行了。在加上<code>Volatile</code>之后就可以保证该操作的原子性了。</p>
<h3 id="注意"><a href="#注意" class="headerlink" title="注意"></a>注意</h3><blockquote>
<p>volatile在保障可见性的时候仅仅只能保障能够读取到该共享变量的相对新值，对于引用类型变量和数组类型的变量，volatile能保证的也仅仅是该变量本身的可见性，而对于数组中的元素，引用类型中的字段（实例变量，静态变量）则无法保证其可见性，对于这些变量可见性的保障可以利用JUC工具包中的<code>Atomic原子类</code>。</p>
</blockquote>
<h2 id="内存屏障"><a href="#内存屏障" class="headerlink" title="内存屏障"></a>内存屏障</h2><p>先简单了解两个指令：</p>
<ul>
<li>Store：将处理器缓存的数据刷新到内存中。</li>
<li>Load：将内存存储的数据拷贝到处理器的缓存中。</li>
</ul>
<table>
<thead>
<tr>
<th>屏障类型</th>
<th>指令示例</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td>LoadLoad Barriers</td>
<td>Load1;<code>LoadLoad</code>;Load2</td>
<td>该屏障确保Load1数据的装载先于Load2及其后所有装载指令的的操作</td>
</tr>
<tr>
<td>StoreStore Barriers</td>
<td>Store1;<code>StoreStore</code>;Store2</td>
<td>该屏障确保Store1立刻刷新数据到内存(使其对其他处理器可见)的操作先于Store2及其后所有存储指令的操作</td>
</tr>
<tr>
<td>LoadStore Barriers</td>
<td>Load1;<code>LoadStore</code>;Store2</td>
<td>确保Load1的数据装载先于Store2及其后所有的存储指令刷新数据到内存的操作</td>
</tr>
<tr>
<td>StoreLoad Barriers</td>
<td>Store1;<code>StoreLoad</code>;Load2</td>
<td>该屏障确保Store1立刻刷新数据到内存的操作先于Load2及其后所有装载指令的操作。它会使该屏障之前的所有内存访问指令(存储指令和访问指令)完成之后,才执行该屏障之后的内存访问指令</td>
</tr>
</tbody></table>
<p>StoreLoad Barriers同时具备其他三个屏障的效果，因此也称之为<code>全能屏障</code>（mfence），是目前大多数处理器所支持的；但是相对其他屏障，该屏障的开销相对昂贵</p>
<ul>
<li><p>按照可见性划分，内存屏障可以分为<strong>加载屏障</strong>(Load Barrier)，和<strong>存储屏障</strong>(Store Barrier)</p>
</li>
<li><p>按照有序性划分可分为<strong>获取屏障</strong>(Acquire Barrier)和<strong>释放屏障</strong>(Release Barrier)</p>
</li>
</ul>
<p>具体那个充当加载屏障，那个充当存储屏障，我并不想讨论，各种博客各种资料各有各的说法，其实关于究竟底层是如何实现，如何插入，插入的哪一种这些细节我们不用去关心，不同的CPU不同的架构实现的方式都不一样，太过深入也没有多大的意义，很多博客介绍的也<code>完全不同</code>，我们只需要知道大概的原理就行了。如果想了解更多可以参考下列文章</p>
<p><a class="link"   target="_blank" rel="noopener" href="http://www.0xffffff.org/2017/02/21/40-atomic-variable-mutex-and-memory-barrier/" >聊聊原子变量、锁、内存屏障那点事<i class="fas fa-external-link-alt"></i></a></p>
<p><a class="link"   target="_blank" rel="noopener" href="https://monkeysayhi.github.io/2017/12/28/%E4%B8%80%E6%96%87%E8%A7%A3%E5%86%B3%E5%86%85%E5%AD%98%E5%B1%8F%E9%9A%9C/" >一文解决内存屏障<i class="fas fa-external-link-alt"></i></a></p>
<p><a class="link"   target="_blank" rel="noopener" href="https://www.jianshu.com/p/506c1e38a922" >面试必问的volatile，你了解多少？<i class="fas fa-external-link-alt"></i></a></p>
<p><a class="link"   target="_blank" rel="noopener" href="https://www.infoq.cn/article/java-memory-model-4/?utm_source=infoq&%253Butm_medium=related_content_link&%253Butm_campaign=relatedContent_articles_clk" >深入理解 Java 内存模型（四）——volatile<i class="fas fa-external-link-alt"></i></a></p>
<h3 id="锁与内存屏障"><a href="#锁与内存屏障" class="headerlink" title="锁与内存屏障"></a>锁与内存屏障</h3><p>用<strong>OneNote</strong>画了几张图</p>
<p><img  
                     lazyload
                     src="/images/loading.svg"
                     data-src="http://static.imlgw.top///20190428/EUPpWHTPnlH8.png?imageslim"
                      alt="mark"
                ></p>
<p>实际上锁就是通过<strong>内存屏障</strong>来保证了有序性和可见性，通过<strong>互斥排它</strong>来保证了原子性</p>
<h3 id="Volatile和内存屏障"><a href="#Volatile和内存屏障" class="headerlink" title="Volatile和内存屏障"></a>Volatile和内存屏障</h3><p><strong>Volatile写操作和内存屏障</strong></p>
<p><img  
                     lazyload
                     src="/images/loading.svg"
                     data-src="http://static.imlgw.top///20190428/PImPUeLGqau6.png?imageslim"
                      alt="volatile变量的写操作"
                ></p>
<p>🔔 写线程对于<code>volatile变量的写操作</code>会产生类似于<code>锁释放</code>的效果。在写完成后会<code>冲刷处理器缓存</code>将结果立即刷新到主存中让其他处理器对应的缓存行失效，让其他处理器可同步该数据</p>
<blockquote>
<p>volatile变量在原子性方面仅仅保证对被修饰的变量的读写<code>本身</code>的原子性。也就是说这个操作不能涉及任何共享变量(包括volatile变量本身)的访问，比如 volatile1=volatile2+1，volatile++ 这样的操作无法保证它的原子性，另外，voaltile可以保证<code>long</code>和<code>double</code>变量在<code>32位</code>机上写的原子性</p>
</blockquote>
<p>这里我们再回头看看前面的单例的例子</p>
<p>①分配对象所需的空间 objRef=allocate(SingleObjectPlus.class);</p>
<p>②初始化引用的对象 invokeConstructor(objRef);</p>
<p>③设置singletonObjectPlus指向刚分配的内存地址 singletonObjectPlus=objRef</p>
<p>虽然这里volatile子保证了子操作③的原子性 但是①②操作只涉及到了局部变量没有涉及到共享变量，由于内存屏障的作用①②操作不可能重排序到③之后，所以可以保证在得到返回之前对象一定已经初始化完毕了，不会出现没初始化完毕就返回的情况</p>
<p><strong>Volatile读操作和内存屏障</strong></p>
<p><img  
                     lazyload
                     src="/images/loading.svg"
                     data-src="http://static.imlgw.top///20190428/tJcC98zgp532.png?imageslim"
                      alt="volatile变量的读操作"
                ></p>
<p>🔔 读线程对于<code>volatile变量的读操作</code>会产生类似于<code>获得锁</code>的效果。读volatile变量前会先<code>刷新处理器缓存</code>从主存或其他处理器缓存中<code>同步</code>该数据</p>
<blockquote>
<p>volatile只能保证读线程读到共享变量的相对新值，对于引用类型和数组类型的并不能保证实例的字段或数组的元素的相对新值，只是保障了<code>引用地址</code>的相对新值(<code>相对新值</code>表示读的过程中其他线程有可能更改了这个值，对应的还有<code>最新值</code>，读的过程中写线程无法更改)</p>
</blockquote>
<h3 id="Volatile变量的开销-amp-场景"><a href="#Volatile变量的开销-amp-场景" class="headerlink" title="Volatile变量的开销&amp;场景"></a>Volatile变量的开销&amp;场景</h3><p><strong>开销</strong></p>
<p>volatile变量的读写都不会导致上下文切换，所以开销比锁要小，从上面的介绍中可以看出 写一个voaltile会使该操作和该操作前的所有写操作对后面的线程是可见的，所以它的成本会比普通变量大一些但是比锁小一点，读一个volatile变量也会比锁小，但是会比普通变量大因为变量都会从内存或其他处理器高速缓存中去拿无法直接从寄存器中去拿，但是也很快了。</p>
<p><strong>应用场景</strong></p>
<p>🔶 使用volatile变量作为状态标志位，应用程序的某个状态由一个线程设置，其他线程会读取该状态作为后面操作的依据，此时用volatile作为同步机制好处就是一个线程可以及时”通知”另一个线程某种事件(例如掉线重连)而避免使用锁造成较大开销</p>
<p>🔶使用volatile保障可见性，一个线程更新了共享变量其他线程无需加锁也可以看到该更新</p>
<p>🔶volatile bean模式（下面是我的个人理解可以直接跳过）</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Person</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> String firstName;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> String lastName;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">int</span> age;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">Person</span><span class="params">(String firstName,String lastName,<span class="keyword">int</span> age)</span></span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.firstName = firstName;</span><br><span class="line">        <span class="keyword">this</span>.lastName = lastName;</span><br><span class="line">        <span class="keyword">this</span>.age = age;</span><br><span class="line">    &#125;</span><br><span class="line">	<span class="function"><span class="keyword">public</span> String <span class="title">getFirstName</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> firstName; &#125;</span><br><span class="line">	<span class="function"><span class="keyword">public</span> String <span class="title">getLastName</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> lastName; &#125;</span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getAge</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> age; &#125;</span><br><span class="line"> </span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setFirstName</span><span class="params">(String firstName)</span> </span>&#123; </span><br><span class="line">    	<span class="keyword">this</span>.firstName = firstName;</span><br><span class="line">	&#125;</span><br><span class="line"> </span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setLastName</span><span class="params">(String lastName)</span> </span>&#123; </span><br><span class="line">    	<span class="keyword">this</span>.lastName = lastName;</span><br><span class="line">	&#125;</span><br><span class="line"> </span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setAge</span><span class="params">(<span class="keyword">int</span> age)</span> </span>&#123; </span><br><span class="line">    	<span class="keyword">this</span>.age = age;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>关于这个场景的我理解的就是会保证类似volatile Person  preson=new Person(xx,xx,xx,xx);这样的操作具有可见性或者说完整性，不会初始化一半就返回对象要么为null要么就初始化完毕，类似于上面的提到的dcl单例的例子。</p>
<p>🔶简易读写锁</p>
<p>允许读线程读取的时候写线程进行更新，典型的例子就是实现一个计数器如下</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Counter</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">int</span> value;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getValue</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> value; &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">int</span> <span class="title">increment</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> value++;</span><br><span class="line">    &#125;  	</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>想了解更多去看看IBM这篇文章<a class="link"   target="_blank" rel="noopener" href="https://www.ibm.com/developerworks/cn/java/j-jtp06197.html" >Java 理论与实践:正确使用 Volatile 变量<i class="fas fa-external-link-alt"></i></a></p>
<h2 id="一个小问题"><a href="#一个小问题" class="headerlink" title="一个小问题"></a>一个小问题</h2><p><img  
                     lazyload
                     src="/images/loading.svg"
                     data-src="http://static.imlgw.top///20190428/4CYfervOyHH3.png?imageslim"
                      alt="可见性的例子"
                ></p>
<p>上面可见性的问题，图中的代码如果循环里面加上图中框内类似的代码，会发现即使共享变量上面不加<strong>volatile</strong>程序依然可以正常退出，上面出现的死循环并没有出现 (我的JDK版本是1.8，不同的版本情况可能不一样)，那是不是说这些操作也达到了保证可见性的作用呢？其实仔细分析这几行代码，后面三种都会刷新或冲刷处理器缓存(print里面也是加锁了的)，我一开始觉得可能是这个原因导致的，但是按道理应该是只会保证同步块内部的变量的可见性，但是sleep并没有加锁，是个本地方法为啥还是会导致这样的结果呢？这里我也不想深究了，我感觉也没啥必要了，具体的场景下该加<strong>volatile</strong>还是老老实实加<strong>volatile</strong>，如果继续探究下可以看下<a class="link"   target="_blank" rel="noopener" href="http://www.importnew.com/19434.html" >这篇文章 <i class="fas fa-external-link-alt"></i></a>。</p>
<h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul>
<li><p>《Java多线程编程实战指南：核心篇》</p>
</li>
<li><p><a class="link"   target="_blank" rel="noopener" href="https://blog.csdn.net/javazejian/article/details/72772461" >javazejian<i class="fas fa-external-link-alt"></i></a></p>
</li>
<li><p>…</p>
</li>
</ul>

        </div>

        
            <div class="post-copyright-info">
                <div class="article-copyright-info-container">
    <ul>
        <li>本文标题：Volatile关键字详解</li>
        <li>本文作者：Resolmi</li>
        <li>创建时间：2019-04-29 00:00:00</li>
        <li>
            本文链接：https://imlgw.top/2019/04/29/1b898227/
        </li>
        <li>
            版权声明：本博客所有文章除特别声明外，均采用 <a class="license" target="_blank" rel="noopener" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh">BY-NC-SA</a> 许可协议。转载请注明出处！
        </li>
    </ul>
</div>

            </div>
        

        
            <div class="article-nav">
                
                    <div class="article-prev">
                        <a class="prev"
                           rel="prev"
                           href="/2019/05/02/f8b3ee4a/"
                        >
                            <span class="left arrow-icon flex-center">
                              <i class="fas fa-chevron-left"></i>
                            </span>
                            <span class="title flex-center">
                                <span class="post-nav-title-item">JNI初探</span>
                                <span class="post-nav-item">上一篇</span>
                            </span>
                        </a>
                    </div>
                
                
                    <div class="article-next">
                        <a class="next"
                           rel="next"
                           href="/2019/04/22/2c91a79/"
                        >
                            <span class="title flex-center">
                                <span class="post-nav-title-item">CAS与原子变量</span>
                                <span class="post-nav-item">下一篇</span>
                            </span>
                            <span class="right arrow-icon flex-center">
                              <i class="fas fa-chevron-right"></i>
                            </span>
                        </a>
                    </div>
                
            </div>
        

        
            <div class="comment-container">
                <div class="comments-container">
    <div id="comment-anchor"></div>
    <div class="comment-area-title">
        <i class="fas fa-comments">&nbsp;评论</i>
    </div>
    

        
            <section class="disqus-comments">
<div id="disqus_thread">
  <noscript>Please enable JavaScript to view the <a target="_blank" rel="noopener" href="//disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
</div>
</section>

<script>
var disqus_shortname = 'imlgw';

var disqus_url = 'https://imlgw.top/2019/04/29/1b898227/';

(function(){
  var dsq = document.createElement('script');
  dsq.type = 'text/javascript';
  dsq.async = true;
  dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
  (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>

<script id="dsq-count-scr" src="//imlgw.disqus.com/count.js" async></script>
        
    
</div>

            </div>
        
    </div>
</div>


                
            </div>

        </div>

        <div class="page-main-content-bottom">
            <footer class="footer">
    <div class="info-container">
        <div class="copyright-info info-item">
            &copy;
            
              <span>2018</span>&nbsp;-&nbsp;
            
            2021&nbsp;<i class="fas fa-heart icon-animate"></i>&nbsp;<a href="/">Resolmi</a>
        </div>
        
            <script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
            <div class="website-count info-item">
                
                    <span id="busuanzi_container_site_uv">
                        访问人数&nbsp;<span id="busuanzi_value_site_uv"></span>&ensp;
                    </span>
                
                
                    <span id="busuanzi_container_site_pv">
                        总访问量&nbsp;<span id="busuanzi_value_site_pv"></span>
                    </span>
                
            </div>
        
        
            <div class="icp-info info-item"><a target="_blank" rel="nofollow" href="https://beian.miit.gov.cn">鄂ICP备18011208号</a></div>
        
    </div>
</footer>

        </div>
    </div>

    
        <div class="post-tools">
            <div class="post-tools-container">
    <ul class="tools-list">
        <!-- TOC aside toggle -->
        
            <li class="tools-item page-aside-toggle">
                <i class="fas fa-outdent"></i>
            </li>
        

        <!-- go comment -->
        
            <li class="go-comment">
                <i class="fas fa-comment"></i>
            </li>
        
    </ul>
</div>

        </div>
    

    <div class="right-bottom-side-tools">
        <div class="side-tools-container">
    <ul class="side-tools-list">
        <li class="tools-item tool-font-adjust-plus flex-center">
            <i class="fas fa-search-plus"></i>
        </li>

        <li class="tools-item tool-font-adjust-minus flex-center">
            <i class="fas fa-search-minus"></i>
        </li>

        <li class="tools-item tool-expand-width flex-center">
            <i class="fas fa-arrows-alt-h"></i>
        </li>

        <li class="tools-item tool-dark-light-toggle flex-center">
            <i class="fas fa-moon"></i>
        </li>

        <!-- rss -->
        

        

        <li class="tools-item tool-scroll-to-bottom flex-center">
            <i class="fas fa-arrow-down"></i>
        </li>
    </ul>

    <ul class="exposed-tools-list">
        <li class="tools-item tool-toggle-show flex-center">
            <i class="fas fa-cog fa-spin"></i>
        </li>
        
            <li class="tools-item tool-scroll-to-top flex-center">
                <i class="arrow-up fas fa-arrow-up"></i>
                <span class="percent"></span>
            </li>
        
    </ul>
</div>

    </div>

    
        <aside class="page-aside">
            <div class="post-toc-wrap">
    <div class="post-toc">
        <ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#JMM-amp-CPU%E7%BC%93%E5%AD%98"><span class="nav-number">1.</span> <span class="nav-text">JMM&amp;CPU缓存</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#CPU%E7%BC%93%E5%AD%98"><span class="nav-number">1.1.</span> <span class="nav-text">CPU缓存</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E6%9C%89CPU%E7%BC%93%E5%AD%98%EF%BC%9F"><span class="nav-number">1.1.1.</span> <span class="nav-text">为什么要有CPU缓存？</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E4%BD%BF%E7%94%A8CPU%E7%BC%93%E5%AD%98%E5%B8%A6%E6%9D%A5%E7%9A%84%E9%97%AE%E9%A2%98"><span class="nav-number">1.1.2.</span> <span class="nav-text">使用CPU缓存带来的问题</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88"><span class="nav-number">1.1.3.</span> <span class="nav-text">解决方案</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Java%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B-JMM"><span class="nav-number">1.2.</span> <span class="nav-text">Java内存模型(JMM)</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%86%85%E5%AD%98%E9%97%B4%E4%BA%A4%E4%BA%92"><span class="nav-number">1.2.1.</span> <span class="nav-text">内存间交互</span></a></li></ol></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Volatile%E5%B9%B2%E4%BA%86%E4%BB%80%E4%B9%88%EF%BC%9F"><span class="nav-number">2.</span> <span class="nav-text">Volatile干了什么？</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%BF%9D%E8%AF%81%E5%8F%AF%E8%A7%81%E6%80%A7"><span class="nav-number">2.1.</span> <span class="nav-text">保证可见性</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%BF%9D%E8%AF%81%E6%9C%89%E5%BA%8F%E6%80%A7"><span class="nav-number">2.2.</span> <span class="nav-text">保证有序性</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%BF%9D%E9%9A%9CLong-Double%E5%8F%98%E9%87%8F%E5%86%99%E7%9A%84%E5%8E%9F%E5%AD%90%E6%80%A7"><span class="nav-number">2.3.</span> <span class="nav-text">保障Long&#x2F;Double变量写的原子性</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%B3%A8%E6%84%8F"><span class="nav-number">2.4.</span> <span class="nav-text">注意</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%86%85%E5%AD%98%E5%B1%8F%E9%9A%9C"><span class="nav-number">3.</span> <span class="nav-text">内存屏障</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E9%94%81%E4%B8%8E%E5%86%85%E5%AD%98%E5%B1%8F%E9%9A%9C"><span class="nav-number">3.1.</span> <span class="nav-text">锁与内存屏障</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Volatile%E5%92%8C%E5%86%85%E5%AD%98%E5%B1%8F%E9%9A%9C"><span class="nav-number">3.2.</span> <span class="nav-text">Volatile和内存屏障</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Volatile%E5%8F%98%E9%87%8F%E7%9A%84%E5%BC%80%E9%94%80-amp-%E5%9C%BA%E6%99%AF"><span class="nav-number">3.3.</span> <span class="nav-text">Volatile变量的开销&amp;场景</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%B8%80%E4%B8%AA%E5%B0%8F%E9%97%AE%E9%A2%98"><span class="nav-number">4.</span> <span class="nav-text">一个小问题</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99"><span class="nav-number">5.</span> <span class="nav-text">参考资料</span></a></li></ol>
    </div>
</div>
        </aside>
    

    <div class="image-viewer-container">
    <img src="">
</div>


    
        <div class="search-pop-overlay">
    <div class="popup search-popup">
        <div class="search-header">
          <span class="search-input-field-pre">
            <i class="fas fa-keyboard"></i>
          </span>
            <div class="search-input-container">
                <input autocomplete="off"
                       autocorrect="off"
                       autocapitalize="off"
                       placeholder="搜索..."
                       spellcheck="false"
                       type="search"
                       class="search-input"
                >
            </div>
            <span class="popup-btn-close">
                <i class="fas fa-times"></i>
            </span>
        </div>
        <div id="search-result">
            <div id="no-result">
                <i class="fas fa-spinner fa-pulse fa-5x fa-fw"></i>
            </div>
        </div>
    </div>
</div>

    

</main>



<script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.4.3/source/js/utils.js"></script><script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.4.3/source/js/main.js"></script><script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.4.3/source/js/header-shrink.js"></script><script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.4.3/source/js/back2top.js"></script><script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.4.3/source/js/dark-light-toggle.js"></script>


    <script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.4.3/source/js/local-search.js"></script>



    <script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.4.3/source/js/code-copy.js"></script>



    <script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.4.3/source/js/lazyload.js"></script>


<div class="post-scripts pjax">
    
        <script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.4.3/source/js/left-side-toggle.js"></script><script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.4.3/source/js/libs/anime.min.js"></script><script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.4.3/source/js/toc.js"></script>
    
</div>


    <script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.4.3/source/js/libs/pjax.min.js"></script>
<script>
    window.addEventListener('DOMContentLoaded', () => {
        window.pjax = new Pjax({
            selectors: [
                'head title',
                '.page-container',
                '.pjax'
            ],
            history: true,
            debug: false,
            cacheBust: false,
            timeout: 0,
            analytics: false,
            currentUrlFullReload: false,
            scrollRestoration: false,
            // scrollTo: true,
        });

        document.addEventListener('pjax:send', () => {
            KEEP.utils.pjaxProgressBarStart();
        });

        document.addEventListener('pjax:complete', () => {
            KEEP.utils.pjaxProgressBarEnd();
            window.pjax.executeScripts(document.querySelectorAll('script[data-pjax], .pjax script'));
            KEEP.refresh();
        });
    });
</script>



<script src="https://cdn.jsdelivr.net/npm/live2d-widget@3.x/lib/L2Dwidget.min.js"></script><script>L2Dwidget.init({"pluginRootPath":"live2dw/","pluginJsPath":"lib/","pluginModelPath":"assets/","tagMode":false,"debug":false,"model":{"jsonPath":"https://cdn.jsdelivr.net/npm/live2d-widget-model-hijiki@1.0.5/assets/hijiki.model.json"},"display":{"superSample":2,"width":160,"height":320,"position":"right","hOffset":0,"vOffset":-70},"mobile":{"show":false,"scale":0.2},"log":false});</script></body>
</html>
